﻿using System;
using System.Collections;
using System.Reflection;

namespace Nasa8x.Core
{
    public class ComparerExtension : IComparer
    {

        private string fieldName;
        private bool reverseSort;

        /// <summary>
        /// Creates a SortableComparer for the specified field and sorting direction.
        /// </summary>
        /// <param name="field"></param>
        /// <param name="reverse"></param>
        public ComparerExtension(string field, bool reverse)
        {
            fieldName = field;
            reverseSort = reverse;
        }

        /// <summary>
        /// Returns a value from an object based on a property name.
        /// </summary>
        /// <param name="obj">The source object.</param>
        /// <param name="propReference">The name of the property (or series of properties separated by periods) to return.</param>
        /// <returns>If the reference is valid, the value is returned; otherwise, null is returned.</returns>
        protected object GetPropertyReference(object obj, string propReference)
        {
            object currentValue = obj;
            string[] props = propReference.Split('.');
            foreach (string propName in props)
            {
                try
                {
                    Type objectType = currentValue.GetType();
                    currentValue = objectType.InvokeMember(propName, BindingFlags.GetProperty | BindingFlags.GetField, null, currentValue, new object[0]);
                }
                catch (Exception exc)
                {
                    return null;
                }
            }
            return currentValue;
        }

        /// <summary>
        /// Compares two objects based on the field and sort order specified for this object. The
        /// objects can be of different types, but require the specified fieldName to exist as
        /// a property to function correctly.
        /// </summary>
        /// <param name="a">An object to compare.</param>
        /// <param name="b">An object to compare.</param>
        /// <returns>Returns the result of CompareTo from comparing the two object's properties.</returns>
        public int Compare(object a, object b)
        {
            int returnValue = 0;

            object valueA = GetPropertyReference(a, fieldName);
            object valueB = GetPropertyReference(b, fieldName);

            Type valueTypeA = (valueA != null) ? valueA.GetType() : null;
            Type valueTypeB = (valueB != null) ? valueB.GetType() : null;

            // If both values are null, we can't tell one way or the other how to sort
            if (valueA == null && valueB == null) { returnValue = 0; }
            // If one value is null, sort it down the list
            else if (valueA == null) { returnValue = 1; }
            else if (valueB == null) { returnValue = -1; }
            // If the values are of different types, then we can't sort them
            else if (valueTypeA != valueTypeB && !valueTypeA.IsSubclassOf(valueTypeB) && !valueTypeB.IsSubclassOf(valueTypeA)) { returnValue = 0; }
            // If the values are comparable, then return the results of their CompareTo method
            else if (valueA is IComparable && valueB is IComparable)
            {
                returnValue = (int)valueTypeA.InvokeMember("CompareTo", BindingFlags.InvokeMethod,
                                                            null, valueA, new object[] { valueB });
            }
            // Otherwise, we'll end up returning 0, which means no sorting will take place

            if (reverseSort) { returnValue = returnValue * -1; }
            return returnValue;
        }

    }
}
